import yaml
import os
from .logger import logger, log_function_call
from typing import Dict, Any, List, Optional
import pandas as pd
import re
import platform


def get_chrome_binary_location() -> Optional[str]:
    def find_chrome_on_windows() -> Optional[str]:
        potential_paths = [
            "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
            "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
            os.path.expandvars(r"%LOCALAPPDATA%\Google\Chrome\Application\chrome.exe"),
            os.path.expanduser(r"~\AppData\Local\Google\Chrome\Application\chrome.exe"),
        ]
        for path in potential_paths:
            if os.path.isfile(path):
                return path
        return None

    def find_chrome_on_macos() -> Optional[str]:
        potential_paths = [
            "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
            "/usr/local/bin/google-chrome",
            "/usr/bin/google-chrome",
            "/opt/homebrew/bin/google-chrome",
        ]
        for path in potential_paths:
            if os.path.isfile(path) and os.access(path, os.X_OK):
                return path
        return None

    system = platform.system()
    chrome_path = None

    if system == "Windows":
        chrome_path = find_chrome_on_windows()
    elif system == "Darwin":
        chrome_path = find_chrome_on_macos()
    else:
        logger.warning(
            f"Chrome binary location: Unsupported operating system: {system}"
        )

    if chrome_path:
        current_config_path = (
            load_configuration().get("selenium", {}).get("chrome_binary_location")
        )
        if chrome_path != current_config_path:
            config = load_configuration()
            config.setdefault("selenium", {})["chrome_binary_location"] = chrome_path
            try:
                update_configuration(config)
                logger.info(
                    f"Updated Chrome binary location in config.yml: {chrome_path}"
                )
            except Exception as e:
                logger.error(f"Failed to update config.yml with new Chrome path: {e}")
        return chrome_path
    else:
        logger.error("Chrome binary not found in typical locations.")
        return None


def get_firefox_binary_location() -> Optional[str]:
    def find_firefox_on_windows() -> Optional[str]:
        potential_paths = [
            "C:\\Program Files\\Mozilla Firefox\\firefox.exe",
            "C:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe",
            os.path.expandvars(r"%APPDATA%\Mozilla Firefox\firefox.exe"),
            os.path.expanduser(r"~\AppData\Local\Mozilla Firefox\firefox.exe"),
        ]
        for path in potential_paths:
            if os.path.isfile(path):
                return path
        return None

    def find_firefox_on_macos() -> Optional[str]:
        potential_paths = [
            "/Applications/Firefox.app/Contents/MacOS/firefox",
            "/usr/local/bin/firefox",
            "/opt/homebrew/bin/firefox", 
        ]
        for path in potential_paths:
            if os.path.isfile(path) and os.access(path, os.X_OK):
                return path
        return None

    system = platform.system()
    firefox_path = None

    if system == "Windows":
        firefox_path = find_firefox_on_windows()
    elif system == "Darwin":
        firefox_path = find_firefox_on_macos()
    else:
        logger.warning(f"Firefox binary location: Unsupported operating system: {system}")

    if firefox_path:
        current_config_path = (
            load_configuration().get("selenium", {}).get("firefox_binary_location")
        )
        if firefox_path != current_config_path:
            config = load_configuration()
            config.setdefault("selenium", {})["firefox_binary_location"] = firefox_path
            try:
                update_configuration(config)
                logger.info(
                    f"Updated Firefox binary location in config.yml: {firefox_path}"
                )
            except Exception as e:
                logger.error(f"Failed to update config.yml with new Firefox path: {e}")
        return firefox_path
    else:
        logger.error("Firefox binary not found in typical locations.")
        return None


def get_sort_by_task_id() -> bool:
    """Get the sort_by_task_id option."""
    sort_by_task_id = load_configuration().get("sort_by_task_id", True)

    if isinstance(sort_by_task_id, str):
        return sort_by_task_id.lower() == "true"
    return bool(sort_by_task_id)

def get_streamlit_server_address() -> str:
    """Get the address of the Streamlit server."""
    return load_configuration().get("streamlit", {}).get("address", "127.0.0.1")

def get_streamlit_server_port() -> str:
    """Get the port of the Streamlit server."""
    return load_configuration().get("streamlit", {}).get("port", "8501")

def get_streamlit_allow_run_on_save() -> str:
    """Get the allow_run_on_save option of the Streamlit server."""
    run_on_save_value = (
        load_configuration().get("streamlit", {}).get("allow_run_on_save", False)
    )

    if isinstance(run_on_save_value, str):
        return run_on_save_value.lower() == "true"
    return bool(run_on_save_value)


def get_chrome_driver_path() -> str:
    """Get the path to the ChromeDriver."""
    return load_configuration().get("selenium", {}).get("chrome_driver_path")

def get_firefox_driver_path() -> str:
    """Get the path to the FirefoxDriver."""
    return load_configuration().get("selenium", {}).get("firefox_driver_path")

def get_config_file_path() -> str:
    """Get the path to the configuration file."""
    return "config.yml"


def load_configuration() -> Dict[str, Any]:
    """Load the configuration file as a dictionary."""
    config_path = get_config_file_path()
    if os.path.exists(config_path):
        with open(config_path, "r") as file:
            config = yaml.safe_load(file) or {}
    else:
        logger.error("Configuration file does not exist.")
        config = {}
    return config


def validate_site_name_conflicts() -> bool:
    """Ensure there are no conflicting site names in the configuration."""
    websites = get_website_names()
    for first_site in websites:
        for second_site in websites:
            if first_site == second_site:
                continue
            if first_site in second_site:
                raise ValueError(
                    f"SiteName '{first_site}' is included in '{second_site}'. "
                    "Conflict in SiteName configuration. Please check config.yml."
                )
    return True


def get_headless_mode() -> bool:
    """Retrieve the headless option for Selenium."""
    headless_value = load_configuration().get("selenium", {}).get("headless", False)

    # Normalize the headless value to a boolean
    if isinstance(headless_value, str):
        return headless_value.lower() == "true"
    return bool(headless_value)


def get_secret_excel_path() -> str:
    """Get the path to the secret Excel file."""
    return load_configuration().get("secret_files", {}).get("xlsx")


def get_number_of_tasks() -> int:
    """Get the number of tasks."""
    return len(get_website_names())

def get_number_of_websites() -> int:
    """Get the number of websites configured in config.yml. This is the same as the number of tasks."""
    return get_number_of_tasks()

def get_website_names() -> List[str]:
    """Retrieve the list of website names."""
    return list(load_configuration().get("websites", {}).keys())

def get_task_ids() -> List[str]:
    """Retrieve the list of task IDs."""
    return [map_site_to_task_id()[site] for site in get_website_names()]

def get_sorted_website_names() -> List[str]:
    """Retrieve the list of website names sorted alphabetically."""
    return sorted(get_website_names())


def extract_task_number(task_id: str) -> int:
    """Extracts the numeric part from the task ID."""
    match = re.search(r"\d+", task_id)
    return int(match.group()) if match else 0


def update_configuration(config: Dict[str, Any]) -> None:
    """Update the configuration file.

    Sorts the websites by task ID (index) numerically by default. If `sort_by_task_id` is set to False,
    it will sort the websites alphabetically by their names.

    Args:
        config (Dict[str, Any]): The configuration dictionary to update.
        sort_by_task_id (bool, optional): Determines the sorting method.
            Defaults to True.
    """
    sort_by_task_id = get_sort_by_task_id()
    websites = config.get("websites", {})

    if sort_by_task_id:
        # Sort websites by the numeric value of task ID/index
        sorted_websites = {
            k: v
            for k, v in sorted(
                websites.items(),
                key=lambda item: extract_task_number(
                    item[1].get("index", "TASK00000000")
                ),
            )
        }
    else:
        # Sort websites alphabetically by website name
        sorted_websites = dict(
            sorted(websites.items(), key=lambda item: item[0].lower())
        )

    config["websites"] = sorted_websites

    with open(get_config_file_path(), "w") as file:
        yaml.dump(config, file, sort_keys=False)


def get_sms_website_names() -> List[str]:
    """Retrieve the list of SMS website names."""
    return list(load_configuration().get("sms_websites", {}).keys())


def get_sorted_sms_website_names() -> List[str]:
    """Retrieve the list of SMS website names sorted alphabetically."""
    return sorted(get_sms_website_names())


def map_site_to_task_id() -> Dict[str, str]:
    """Map site names to their corresponding task IDs."""
    mapping = {}
    configuration = load_configuration()
    for site_name, site_data in configuration.get("websites", {}).items():
        mapping[site_name] = site_data.get("index", "TASK00000000")
    return mapping


def map_task_id_to_site() -> Dict[str, str]:
    """Map task IDs to their corresponding site names."""
    return {v: k for k, v in map_site_to_task_id().items()}


def load_secret_excel_dataframes() -> pd.DataFrame:
    """Load the secret Excel file into a pandas DataFrame."""
    secret_excel_path = get_secret_excel_path()
    return pd.read_excel(secret_excel_path)


@log_function_call
def get_secret_data_for_task(task_index: str) -> Optional[Dict[str, Any]]:
    """Load and validate secret Excel data for a specific task index."""
    df_secret = load_secret_excel_dataframes()
    
    matched_rows = df_secret[df_secret["Index"] == task_index]
    
    if matched_rows.empty:
        logger.error(f"No entry found in the secret Excel file for task index {task_index}.")
        return None
    elif len(matched_rows) > 1:
        logger.error(f"Multiple entries found in the secret Excel file for task index {task_index}.")
        return None
        
    secret_series = matched_rows.iloc[0]
    
    return {
        'username': secret_series["Account"],
        'password': secret_series["Password"],
        'url': secret_series["Website"],
        'requires_captcha': secret_series["Captcha"].upper() == "Y"
    }


def map_task_id_to_url() -> Dict[str, str]:
    """Map task IDs to their corresponding URLs."""
    return load_secret_excel_dataframes().set_index("Index")["Website"].to_dict()


def map_url_to_task_id() -> Dict[str, str]:
    """Map URLs to their corresponding task IDs."""
    return {v: k for k, v in map_task_id_to_url().items()}


def map_site_to_url() -> Dict[str, str]:
    """Map site names to their corresponding URLs."""
    site_task_map = map_site_to_task_id()
    task_url_map = map_task_id_to_url()
    return {site: task_url_map.get(task_id) for site, task_id in site_task_map.items()}

def get_start_and_end_date() -> dict:
    """Get start and end dates from config."""
    config = load_configuration()
    dates = config.get('dates', {})
    return {
        'start_date': dates.get('start_date'), 
        'end_date': dates.get('end_date')  
    }



# hhaha 